package cn.flant.util;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.nio.charset.Charset;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.CompressorOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;

import cn.flant.io.IOUtil;

public class CompressUtil {
	private static String charset = "gbk";

	public static String getCharset() {
		return charset == null ? Charset.defaultCharset().name() : charset;
	}

	public static void setCharset(String charset) {
		CompressUtil.charset = charset;
	}

	private static void _compress_0(Class<? extends CompressorOutputStream> streamType, File src, File dest, String fileExt)
			throws IOException {
		if (!src.exists()) {
			throw new FileNotFoundException(src.getAbsolutePath());
		}
		if (dest == null) {
			dest = src.getParentFile();
		} else {
			File path = dest;
			if (dest.getName().toLowerCase().endsWith(fileExt)) {
				path = dest.getParentFile();
			}
			if (!path.exists() && !path.mkdirs()) {
				throw new IOException("can not create dir: " + dest.getAbsolutePath());
			}
		}
		if (dest.isDirectory()) {
			String fileName = src.getName();
			dest = new File(dest.getAbsolutePath() + "/" + fileName + fileExt);
		}
		CompressorOutputStream output = null;
		FileInputStream input = null;
		try {
			output = (CompressorOutputStream) streamType.getConstructor(OutputStream.class).newInstance(new FileOutputStream(dest));
			input = new FileInputStream(src);
			byte[] buffer = new byte[2048];
			int length = -1;
			while ((length = input.read(buffer, 0, buffer.length)) != -1) {
				output.write(buffer, 0, length);
			}
		} catch (IOException e) {
			throw e;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
			IOUtil.close(output);
			IOUtil.close(input);
		}
	}

	private static ArchiveEntry newArchiveEntry(Class<?> clazz, String name, File file) {
		if (clazz.equals(ZipArchiveEntry.class)) {
			return new ZipArchiveEntry(name);
		} else if (clazz.equals(TarArchiveEntry.class)) {
			return new TarArchiveEntry(file, name);
		} else if (clazz.equals(JarArchiveEntry.class)) {
			return new JarArchiveEntry(name);
		} else {
			throw new IllegalArgumentException("unsupported compress type");
		}
	}

	private static ArchiveOutputStream newArchiveOutputStream(Class<?> clazz, OutputStream output) {
		if (clazz.equals(ZipArchiveOutputStream.class)) {
			ZipArchiveOutputStream zos = new ZipArchiveOutputStream(output);
			zos.setEncoding(getCharset());
			return zos;
		} else if (clazz.equals(TarArchiveOutputStream.class)) {
			return new TarArchiveOutputStream(output, getCharset());
		} else if (clazz.equals(JarArchiveOutputStream.class)) {
			JarArchiveOutputStream jos = new JarArchiveOutputStream(output);
			jos.setEncoding(getCharset());
			return jos;
		} else {
			throw new IllegalArgumentException("unsupported compress type");
		}
	}

	private static void _appendFile(Class<?> clazz, ArchiveOutputStream output, String baseDir, File file, FileFilter fileFilter, byte[] buffer)
			throws IOException {
		if (fileFilter != null && !fileFilter.accept(file)) {
			return;
		}
		String entryName = null;
		if (baseDir == null || baseDir.length() == 0) {
			entryName = file.getName();
		} else {
			entryName = baseDir + file.getName();
		}
		if (file.isDirectory()) {
			entryName += "/";
			output.putArchiveEntry(newArchiveEntry(clazz, entryName, file));
			output.closeArchiveEntry();
			for (File child : file.listFiles()) {
				_appendFile(clazz, output, entryName, child, fileFilter, buffer);
			}
		} else {
			FileInputStream input = null;
			try {
				input = new FileInputStream(file);
				output.putArchiveEntry(newArchiveEntry(clazz, entryName, file));
				int length = -1;
				while ((length = input.read(buffer, 0, buffer.length)) != -1) {
					output.write(buffer, 0, length);
				}
				output.closeArchiveEntry();
			} finally {
				IOUtil.close(input);
			}
		}
	}

	private static void _compress_1(Class<? extends ArchiveOutputStream> streamType, Class<?> entryType, File src, File dest, String fileExt,
			FileFilter fileFilter)
			throws IOException {
		if (!src.exists()) {
			throw new FileNotFoundException(src.getAbsolutePath());
		}
		if (dest == null) {
			dest = src.getParentFile();
		} else {
			File path = dest;
			if (dest.getName().toLowerCase().endsWith(fileExt)) {
				path = dest.getParentFile();
			}
			if (!path.exists() && !path.mkdirs()) {
				throw new IOException("can not create dir: " + dest.getAbsolutePath());
			}
		}
		if (dest.isDirectory()) {
			String fileName = src.getName();
			int idx = fileName.lastIndexOf(".");
			if (idx > 0) {
				fileName = fileName.substring(0, idx);
			}
			dest = new File(dest.getAbsolutePath() + "/" + fileName + fileExt);
		}
		ArchiveOutputStream output = null;
		try {
			output = newArchiveOutputStream(streamType, new FileOutputStream(dest));
			byte[] buffer = new byte[2048];
			_appendFile(entryType, output, "", src, fileFilter, buffer);
		} catch (IOException e) {
			throw e;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
			IOUtil.close(output);
		}
	}

	private static void _decompress_1(Class<? extends ArchiveInputStream> streamType, File src, File dest) throws IOException {
		if (!src.exists()) {
			throw new FileNotFoundException(src.getAbsolutePath());
		}
		if (dest == null) {
			String fileName = src.getName();
			int idx = fileName.lastIndexOf(".");
			if (idx > 0) {
				fileName = fileName.substring(0, idx);
			} else {
				throw new IOException("file is exists, please choose another: " + src.getAbsolutePath());
			}
			dest = new File(src.getParent() + "/" + fileName);
		}
		if (!dest.exists() && !dest.mkdirs()) {
			throw new IOException("can not create dir: " + dest.getAbsolutePath());
		}
		ArchiveInputStream input = null;
		try {
			Constructor<?> construct = null;
			try {
				construct = streamType.getConstructor(InputStream.class, String.class);
				input = (ArchiveInputStream) construct.newInstance(new FileInputStream(src), getCharset());
			} catch (Throwable e) {
				construct = streamType.getConstructor(InputStream.class);
				input = (ArchiveInputStream) construct.newInstance(new FileInputStream(src));
			}
			byte[] buffer = new byte[2048];
			ArchiveEntry entry = null;
			while ((entry = input.getNextEntry()) != null) {
				File entryPath = new File(dest.getAbsolutePath() + "/" + entry.getName());
				if (entry.isDirectory()) {
					if (!entryPath.exists() && !entryPath.mkdirs()) {
						throw new IOException("can not create dir: " + entryPath.getAbsolutePath());
					}
				} else {
					File dir = entryPath.getParentFile();
					if (!dir.exists() && !dir.mkdirs()) {
						throw new IOException("can not create dir: " + dir.getAbsolutePath());
					}
					FileOutputStream output = null;
					try {
						output = new FileOutputStream(entryPath);
						int length = -1;
						while ((length = input.read(buffer, 0, buffer.length)) != -1) {
							output.write(buffer, 0, length);
						}
						output.flush();
					} finally {
						IOUtil.close(output);
					}
				}
			}
		} catch (IOException e) {
			throw e;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
			if (input != null) {
				try {
					input.close();
				} catch (Exception e) {
				}
			}
		}
	}

	private static void _decompress_0(Class<? extends CompressorInputStream> streamType, File src, File dest) throws IOException {
		if (!src.exists()) {
			throw new FileNotFoundException(src.getAbsolutePath());
		}
		if (dest == null) {
			String fileName = src.getName();
			int idx = fileName.lastIndexOf(".");
			if (idx > 0) {
				fileName = fileName.substring(0, idx);
			} else {
				throw new IOException("file is exists, please choose another: " + src.getAbsolutePath());
			}
			dest = new File(src.getParent() + "/" + fileName);
		}
		File destParent = dest.getParentFile();
		if (!destParent.exists() && !destParent.mkdirs()) {
			throw new IOException("can not create dir: " + destParent.getAbsolutePath());
		}
		CompressorInputStream input = null;
		FileOutputStream output = null;
		try {
			Constructor<?> construct = streamType.getConstructor(InputStream.class);
			input = (CompressorInputStream) construct.newInstance(new FileInputStream(src));
			byte[] buffer = new byte[2048];
			output = new FileOutputStream(dest);
			int length = -1;
			while ((length = input.read(buffer, 0, buffer.length)) != -1) {
				output.write(buffer, 0, length);
			}
			output.flush();
		} catch (IOException e) {
			throw e;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
			IOUtil.close(input);
			IOUtil.close(output);
		}
	}

	public static void zip(File src, File dest, FileFilter filter) throws IOException {
		_compress_1(ZipArchiveOutputStream.class, ZipArchiveEntry.class, src, dest, ".zip", filter);
	}

	public static void zip(File src, File dest) throws IOException {
		zip(src, dest, null);
	}

	public static void zip(File src, FileFilter filter) throws IOException {
		zip(src, null, filter);
	}

	public static void zip(File src) throws IOException {
		zip(src, (FileFilter) null);
	}

	public static void zip(String src, String dest) throws IOException {
		zip(new File(src), new File(dest));
	}

	public static void zip(String src) throws IOException {
		zip(new File(src));
	}

	public static void unzip(File src, File dest) throws IOException {
		_decompress_1(ZipArchiveInputStream.class, src, dest);
	}

	public static void unzip(File src) throws IOException {
		unzip(src, null);
	}

	public static void unzip(String src, String dest) throws IOException {
		unzip(new File(src), new File(dest));
	}

	public static void unzip(String src) throws IOException {
		unzip(new File(src));
	}

	public static void gzip(File src, File dest) throws IOException {
		_compress_0(GzipCompressorOutputStream.class, src, dest, ".gz");
	}

	public static void gzip(File src) throws IOException {
		gzip(src, null);
	}

	public static void gzip(String src, String dest) throws IOException {
		gzip(new File(src), new File(dest));
	}

	public static void gzip(String src) throws IOException {
		gzip(new File(src));
	}

	public static void ungzip(File src, File dest) throws IOException {
		_decompress_0(GzipCompressorInputStream.class, src, dest);
	}

	public static void ungzip(File src) throws IOException {
		ungzip(src, null);
	}

	public static void ungzip(String src, String dest) throws IOException {
		ungzip(new File(src), new File(dest));
	}

	public static void ungzip(String src) throws IOException {
		ungzip(new File(src));
	}

	public static void tar(File src, File dest, FileFilter filter) throws IOException {
		_compress_1(TarArchiveOutputStream.class, TarArchiveEntry.class, src, dest, ".tar", filter);
	}

	public static void tar(File src, File dest) throws IOException {
		tar(src, dest, null);
	}

	public static void tar(File src, FileFilter filter) throws IOException {
		tar(src, null, filter);
	}

	public static void tar(File src) throws IOException {
		tar(src, (FileFilter) null);
	}

	public static void tar(String src, String dest) throws IOException {
		tar(new File(src), new File(dest));
	}

	public static void tar(String src) throws IOException {
		tar(new File(src));
	}

	public static void untar(File src, File dest) throws IOException {
		_decompress_1(TarArchiveInputStream.class, src, dest);
	}

	public static void untar(File src) throws IOException {
		untar(src, null);
	}

	public static void untar(String src, String dest) throws IOException {
		untar(new File(src), new File(dest));
	}

	public static void untar(String src) throws IOException {
		untar(new File(src));
	}

	public static void jar(File src, File dest, FileFilter filter) throws IOException {
		_compress_1(JarArchiveOutputStream.class, JarArchiveEntry.class, src, dest, ".jar", filter);
	}

	public static void jar(File src, File dest) throws IOException {
		jar(src, dest, null);
	}

	public static void jar(File src, FileFilter filter) throws IOException {
		jar(src, null, filter);
	}

	public static void jar(File src) throws IOException {
		jar(src, (FileFilter) null);
	}

	public static void jar(String src, String dest) throws IOException {
		jar(new File(src), new File(dest));
	}

	public static void jar(String src) throws IOException {
		jar(new File(src));
	}

	public static void unjar(File src, File dest) throws IOException {
		_decompress_1(JarArchiveInputStream.class, src, dest);
	}

	public static void unjar(File src) throws IOException {
		unjar(src, null);
	}

	public static void unjar(String src, String dest) throws IOException {
		unjar(new File(src), new File(dest));
	}

	public static void unjar(String src) throws IOException {
		unjar(new File(src));
	}

	public static void main(String[] args) {
		try {
			// File f = new File("J:/snapshot-1-2014-03-18-15-38-54-1.xcm.gz");
			// CompressUtil.ungzip(f);
			File f = new File("J:/snapshot-1-2014-03-18-15-38-54-1.xcm");
			CompressUtil.zip(f, new File("j:/test.zip"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
